package org.sanguine.tests.protei.springapp.mvc.dao;

import org.sanguine.tests.protei.springapp.mvc.models.Entity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.stereotype.Repository;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.logging.Logger;

/**
 * Created by alex on 25.02.14.
 * Author: Alex Kuznetsov
 */

/**
 * DAO для работы с базой данных MySQL, доступной через JNDI по пути "jdbc/mysql" (по умолчанию);
 * @param <T> - класс элемента для хранилища
 */

@Repository ("JndiMySQLDao")
@Scope(value = "prototype")
public class MySQLEntityDAO<T extends Entity> implements EntityDAO<T> {
    private final static Logger log = Logger.getLogger(MySQLEntityDAO.class.getName());
    private final static String DATA_SOURCE_CONTEXT = "jdbc/mysql";
    private final static String DATABASE_NAME = "test";
    private final static String SELECT_DATABASE = "USE %s";
    private final static String CHECK_TABLE = "SHOW TABLES LIKE '%s'";
    private final static String DELETE_QUERY = "DELETE FROM %s WHERE %s = ?";
    private final static String INSERT_QUERY = "INSERT INTO %s %s";
    private final static String FIND_BY_KEY_QUERY = "SELECT * FROM %s WHERE %s = ?";
    private final static String FIND_ALL_QUERY = "SELECT * FROM %s ORDER BY %s ASC LIMIT %d OFFSET %d";
    private final static String COUNT_QUERY = "SELECT COUNT(*) FROM %s";
    private final static String UPDATE_QUERY = "UPDATE %s SET %s";

    private SchemaBean schemaBean;
    private JdbcTemplate jdbcTemplate;
    private DataSource dataSource;
    private Class<T> genericType;
    private String tableName;

    @Autowired
    private MockDatastore<T> mockDatastore;

    @Autowired
    private void setSchemaBean(SchemaBean bean) {
        schemaBean = bean;
    }

    public MySQLEntityDAO() {
        this(DATA_SOURCE_CONTEXT);
    }

    public MySQLEntityDAO(String jndiPath) {
        if (jndiPath == null || jndiPath.isEmpty()) jndiPath = DATA_SOURCE_CONTEXT;
        log.info("Trying to get " + jndiPath);
        dataSource = getJNDIDataSource(jndiPath);
        if (dataSource != null) jdbcTemplate = new JdbcTemplate(dataSource);
        else {
            log.severe("Can't get DataSource for " + jndiPath);
            log.severe("Switch to mock datastore");
            return;
        }
        log.info("jdbcTemplate is " + jdbcTemplate);
    }

    @Override
    public void setEntityType(Class<T> entityClass) {
        if (entityClass == null) return;
        genericType = entityClass;
        log.info("generic type class " + genericType.getName());
        tableName = genericType.getSimpleName();
        log.info("DAO services " + tableName);
        if (jdbcTemplate != null) createSchema();
    }


    private void createSchema() {
        if (schemaBean != null) {
            schemaBean.setDatabaseName(DATABASE_NAME);
            schemaBean.setEntityClass(genericType);
        }
        if (jdbcTemplate == null) return;
        jdbcTemplate.execute(String.format(SELECT_DATABASE, DATABASE_NAME));
        String result = jdbcTemplate.query(String.format(CHECK_TABLE, tableName), new ResultSetExtractor<String>() {
            @Override
            public String extractData(ResultSet resultSet) throws SQLException, DataAccessException {
                if (resultSet.first()) return resultSet.getString(1);
                else return null;
            }
        });
        if (result == null) {
            //Create schema
            if (schemaBean != null) {
                String createQuery = schemaBean.getDDLExpression();
                if (createQuery != null) {
                    String[] query = createQuery.split(";");
                    for (String s : query) {
                        if (!s.isEmpty()) jdbcTemplate.execute(s);
                    }
                }
            }
        }
    }

/*
    private Class<T> guessParametricClass() {
//        Class<T> genericType = (Class<T>) GenericTypeResolver.resolveTypeArgument(getClass(), EntityDAO.class);


        EntityDAO<T> clazz = new MySQLEntityDAO<T>(true){};

//        ParameterizedType type = (ParameterizedType)clazz.getClass().getGenericSuperclass();

//        Class<T> genericType = (Class<T>)type.getActualTypeArguments()[0];

        Class<?> superClass = clazz.getClass(); // initial value
        Type superType;
        do {
            superType = superClass.getGenericSuperclass();
            superClass = extractClassFromType(superType);
        } while (! (superClass.equals(T.class)));

        Type actualArg = ((ParameterizedType)superType).getActualTypeArguments()[0];
        return (Class<B>)extractClassFromType(actualArg);
*/
/*
        TypeToken<T> token = new TypeToken<T>(getClass()) {};



        Type type = token.getRawType();
        Class<T> genericType = (Class<T>)type.getClass();
*//*


        return genericType;
    }
*/

    private DataSource getJNDIDataSource(String dataSourceProvider) {

        if (dataSourceProvider == null || dataSourceProvider.isEmpty()) return null;

        try {
            Context initialContext = new InitialContext();
            if ( initialContext == null){
                log.severe("JNDI problem. Cannot get InitialContext.");
            }
            DataSource datasource = (DataSource)initialContext.lookup(dataSourceProvider);
            if (datasource != null) {
                return datasource;
            }
            else {
                log.severe("Failed to lookup datasource.");
            }
        }
        catch ( NamingException ex ) {
            log.severe("Cannot get datasource: " + ex);
        }
        return null;
    }

    @Override
    public T findByKey(String key) {
        if (jdbcTemplate == null) {
            if (mockDatastore != null) return mockDatastore.findByKey(key);
            else return null;
        }
        if (schemaBean == null || key == null) return null;
        String keyName = schemaBean.getKeyName();
        if (keyName == null || keyName.isEmpty()) return null;
        jdbcTemplate.execute(String.format(SELECT_DATABASE, DATABASE_NAME));
        T result = jdbcTemplate.query(String.format(FIND_BY_KEY_QUERY, DATABASE_NAME + "." + tableName, keyName), new ResultSetExtractor<T>() {
            @Override
            public T extractData(ResultSet resultSet) throws DataAccessException {
                try {
                    if (!resultSet.first()) return null;
                } catch (SQLException e) {
                    log.warning("Null resultset");
                    return null;
                }
                return (T)schemaBean.createEntityInstance(resultSet);
            }
        }, schemaBean.getNativeKeyValue(key));
        return result;
    }

    @Override
    public boolean findAll(int page, int length, List<T> list) {
        if (jdbcTemplate == null) {
            if (mockDatastore != null) return mockDatastore.findAll(page, length, list);
            else return false;
        }

        if (schemaBean == null || schemaBean.getKeyName() == null || schemaBean.getKeyName().isEmpty()) {
            list.clear();
            return false;
        }
        jdbcTemplate.execute(String.format(SELECT_DATABASE, DATABASE_NAME));
        List<T> results = jdbcTemplate.query(String.format(FIND_ALL_QUERY, tableName, schemaBean.getKeyName(), length, page*length),
                new RowMapper<T>() {
                    @Override
                    public T mapRow(ResultSet resultSet, int i) throws SQLException {
                        return (T)schemaBean.createEntityInstance(resultSet);
                    }
                });
        list.clear();
        list.addAll(results);
        jdbcTemplate.execute(String.format(SELECT_DATABASE, DATABASE_NAME));
        Integer countRecords = jdbcTemplate.queryForObject(String.format(COUNT_QUERY, tableName), Integer.class);
        boolean partial = (countRecords > (page*length + results.size()));

        return partial;
    }

    @Override
    public T addEntity(T newEntity) {

        if (jdbcTemplate == null) {
            if (mockDatastore != null) return mockDatastore.addEntity(newEntity);
            else return null;
        }

        if (schemaBean == null) return null;
        final String clause = schemaBean.getColumnsClause(newEntity, false);
        if (clause == null || clause.isEmpty()) return null;
        GeneratedKeyHolder keys = new GeneratedKeyHolder();
        jdbcTemplate.execute(String.format(SELECT_DATABASE, DATABASE_NAME));
        int qRows = jdbcTemplate.update(new PreparedStatementCreator() {
            @Override
            public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
                PreparedStatement statement = dataSource.getConnection().prepareStatement(String.format(INSERT_QUERY, DATABASE_NAME + "." + tableName, clause), PreparedStatement.RETURN_GENERATED_KEYS);
                return statement;
            }
        }, keys);

        if (qRows == 1 && keys.getKey() != null) {
            T createdEntity = findByKey(schemaBean.nativeKeyToString(keys.getKey()));
            return createdEntity;
        }
        return null;
    }

    @Override
    public boolean removeEntity(String key) {
        if (jdbcTemplate == null) {
            if (mockDatastore != null) return mockDatastore.removeEntity(key);
            else return false;
        }

        if (key == null || schemaBean == null) return false;
        String keyName = schemaBean.getKeyName();
        if (keyName == null || keyName.isEmpty()) return false;
        jdbcTemplate.execute(String.format(SELECT_DATABASE, DATABASE_NAME));
        int qRows = jdbcTemplate.update(String.format(DELETE_QUERY, tableName, keyName), schemaBean.getNativeKeyValue(key));
        return (qRows == 1);
    }

    @Override
    public boolean updateEntity(T entity) {
        if (jdbcTemplate == null) {
            if (mockDatastore != null) return mockDatastore.updateEntity(entity);
            else return false;
        }

        if (schemaBean == null || entity == null) return false;
        final String clause = schemaBean.getColumnsClause(entity, true);
        if (clause == null || clause.isEmpty()) return false;
        jdbcTemplate.execute(String.format(SELECT_DATABASE, DATABASE_NAME));
        int qRows = jdbcTemplate.update(String.format(UPDATE_QUERY, tableName, clause));
        return (qRows == 1);
    }
}
